#include "files/file.h"
#include "functional/string.h"
#include "media_info/media_info.h"
#include "utf8/utf8.h"

#include <sstream>

namespace Tvimeg
{
	const std::wstring FORMAT_STRING(L"Format");
	const std::wstring DURATION_STRING(L"Duration");
	const std::wstring WIDTH_STRING(L"Width");
	const std::wstring HEIGHT_STRING(L"Height");
	const std::wstring ASPECT_RATIO_STRING(L"DisplayAspectRatio");
	const std::wstring FRAME_RATE_STRING(L"FrameRate");
	const std::wstring CHANNELS_STRING(L"Channel(s)");

	class MediaInfoInitialiser
	{
	public:
		MediaInfoInitialiser(void)
		{
			MediaInfoLib::MediaInfo::Option_Static(L"Info_Version", L"0.7.47;" PACKAGE_NAME ";" PACKAGE_VERSION);
		}
	};

	MediaInfo::MediaInfo(const File& fileIn)
		:	file(fileIn), duration(0), width(0), height(0), aspectRatio(0.0), frameRate(0.0), channels(0)
	{
		Initialise();

		// create the media info object and open the file
		MediaInfoLib::MediaInfo info;

		if (tolower(file.GetExtension()) == "avi")
		{
			// modified loading code for avi files, as just using the Open method is quite slow
			if (info.Open_Buffer_Init(file.GetSize()) == 1 && file.OpenRead())
			{
				bool needMoreData = true;
				bool hasMoreData = true;

				size_t loopCount = 0;
				const size_t maxLoops = 64;

				while (needMoreData && hasMoreData && loopCount < maxLoops)
				{
					const unsigned long long seekTo = info.Open_Buffer_Continue_GoTo_Get();
					if (seekTo != static_cast<unsigned long long>(-1))
					{
						const bool temp = needMoreData;
						needMoreData = !needMoreData;
						needMoreData = temp;
					}

					const size_t bufferSize = 1024;
					unsigned char buffer[bufferSize];
					size_t dataRead = 0;

					if (!file.Read(buffer, bufferSize, dataRead))
					{
						break;
					}

					hasMoreData = dataRead == bufferSize;

					size_t result = info.Open_Buffer_Continue(buffer, dataRead);
					needMoreData = !GetInfo(info) && ((result & 8) != 8);

					++loopCount;
				}

				info.Open_Buffer_Finalize();
				GetInfo(info);

				file.Close();
			}
		}
		else
		{
			// convert the file name to utf16
			std::wstring fileName;
			ConvertUTF8ToUTF16(fileIn.GetPath(), fileName);

			// load the info from the file
			if (info.Open(fileName))
			{
				GetInfo(info);
			}
		}
	}

	void MediaInfo::Initialise(void)
	{
		static MediaInfoInitialiser initialiser;
	}

	void MediaInfo::ConvertUTF16ToUTF8(const std::wstring& stringIn, std::string& stringOut)
	{
		utf8::utf16to8(stringIn.begin(), stringIn.end(), std::back_inserter(stringOut));
	}

	void MediaInfo::ConvertUTF8ToUTF16(const std::string& stringIn, std::wstring& stringOut)
	{
		utf8::utf8to16(stringIn.begin(), stringIn.end(), std::back_inserter(stringOut));
	}

	int MediaInfo::ConvertToInt(const std::wstring& stringIn)
	{
		std::string utf8String;
		ConvertUTF16ToUTF8(stringIn, utf8String);

		std::stringstream stream(utf8String);

		int temp = 0;
		stream >> temp;

		return temp;
	}

	float MediaInfo::ConvertToFloat(const std::wstring& stringIn)
	{
		std::string utf8String;
		ConvertUTF16ToUTF8(stringIn, utf8String);

		std::stringstream stream(utf8String);

		float temp = 0;
		stream >> temp;

		return temp;
	}

	void MediaInfo::GetInt(MediaInfoLib::MediaInfo& info, const MediaInfoLib::stream_t stream, const std::wstring& name, int& value)
	{
		if (value == 0)
		{
			value = ConvertToInt(info.Get(stream, 0, name));
		}
	}

	void MediaInfo::GetFloat(MediaInfoLib::MediaInfo& info, const MediaInfoLib::stream_t stream, const std::wstring& name, float& value)
	{
		if (value == 0.0)
		{
			value = ConvertToFloat(info.Get(stream, 0, name));
		}
	}

	void MediaInfo::GetString(MediaInfoLib::MediaInfo& info, const MediaInfoLib::stream_t stream, const std::wstring& name, std::string& value)
	{
		if (value.empty())
		{
			ConvertUTF16ToUTF8(info.Get(stream, 0, name), value);
		}
	}

	bool MediaInfo::GetInfo(MediaInfoLib::MediaInfo& info)
	{
		GetString(info, MediaInfoLib::Stream_General, FORMAT_STRING, containerFormat);

		GetInt(info, MediaInfoLib::Stream_Video, DURATION_STRING, duration);
		GetString(info, MediaInfoLib::Stream_Video, FORMAT_STRING, videoFormat);
		GetInt(info, MediaInfoLib::Stream_Video, WIDTH_STRING, width);
		GetInt(info, MediaInfoLib::Stream_Video, HEIGHT_STRING, height);
		GetFloat(info, MediaInfoLib::Stream_Video, ASPECT_RATIO_STRING, aspectRatio);
		GetFloat(info, MediaInfoLib::Stream_Video, FRAME_RATE_STRING, frameRate);

		GetString(info, MediaInfoLib::Stream_Audio, FORMAT_STRING, audioFormat);
		GetInt(info, MediaInfoLib::Stream_Audio, CHANNELS_STRING, channels);

		return !containerFormat.empty() && duration != 0 && !videoFormat.empty() && height != 0 && width != 0 && aspectRatio != 0.0 && frameRate != 0.0 && audioFormat.empty() && channels != 0;
	}
}
